/*
	title: jQuery.jQselectable.js (ex jQuery.selectable.js)
	required: jQuery(tested on 1.4.2)
	encoding: UTF-8
	copy: Copyright 2008-2010 nori (norimania@gmail.com)
	license: MIT
	author: 5509 - http://5509.me/
	archive: http://jqselectable.googlecode.com/
	modified: 2010-12-02 14:00
	rebuild: 2009-09-16 22:48
	date: 2008-09-14 02:34
 */
define(function(require, exports, module){
	var jQuery = require('jquery');
	//require('Form/jqSelecTable/skin/selectable/style.css');
	(function($) {
		// jQuery.jQselectable
		// Make selectbox so usuful and accesible
		// @ 2010-01-09
		var jQselectable = function(select, options, temp) {
			this.conf = {
				style: 'selectable', // 'selectable' or 'simple'
				theme: 'selectable', // 'natural' or 'selectable'
				set: 'fadeIn', // 'show', 'slideDown' or 'fadeIn'
				out: 'hide', // 'hide', 'slideUp' or 'fadeOut'
				setDuration: 'fast', // 'slow', 'normal', 'fast' or int(millisecond)
				outDuration: 'normal',
				opacity: 0.9, // pulldown opacity
				top: 0,
				left: 0,
				callback: null
			}
			this.temp = {
				selectable: '<div class="sctble_cont"/>',
				simpleBox: '<div class="simple_cont"/>'
			}

			// Extend confs and temps by user options
			var _this = this;
			$.seaBase.run(this.conf, options, function(config){
				_this.conf = config;
				require.async('Form/jqSelecTable/skin/'+config.theme+'/style.css', function(){
					$(select).attr('id', config.id).addClass(config.style);
				});
			});
			$.extend(this.temp, temp || {});
			this.target = $(select);
			this.matHeight = 0;
			this.attrs = {
				id: this.target.attr('id'),
				cl: this.target.attr('class')
			}
			this.generatedFlg = false;

			// Init start
			this.init();
		}

		jQselectable.prototype = {
			// Init selectable
			// @ 10-01-09 21:00
			init: function() {
				// Build selectable
				this.build();
				// Event apply
				this.bind_events();
				// Switch flag true
				this.generatedFlg = true;
			},

			// Rebuild selectable
			// @ 09-09-18 17:28
			rebuild: function() {

				//console.log('called rebuild');

				// unbind events from elements related selectable
				this.m_input.unbind();
				this.mat.unbind();
				$('a',this.mat).unbind();
				$('label[for="'+this.attrs.id+'"]').unbind();

				// Build selectable
				this.build();

				// Event apply
				this.bind_events();
			},

			// Building selectable from original select element
			// @ 2010-01-09 21:00
			build: function() {

				// Declare flag
				var has_optgroup = $('optgroup',this.target).length>0 ? true : false;

				var _this = this;
				var generate_anchors = function(obj, parent) {
					var _a = $('<a/>');
					$(parent).append(_a);

					_a.text(obj.text()).attr({
						href: '#'+encodeURI(obj.text()),
						name: obj.val()
					});

					if ( obj.is(':selected') ) {
						_this.m_text.text(obj.text());
						_a.addClass('selected');
					}
					if ( obj.hasClass('br') ) {
						_a.after('<br/>');
					}
				}

				if ( !this.m_input ) {
					this.m_input = $('<a/>');
					this.m_text = $('<span/>');
					var _style = this.conf.style.match(/simple/) ? 'sBox' : 'sctble';

					this.m_input.append(this.m_text).attr({
						id: this.attrs.id+'_dammy',
						href: '#'
					}).addClass('sctble_display').addClass(_style).addClass(this.attrs.cl).insertAfter(this.target);
					this.target.hide();
					this.mat = $('<div/>');

					// Customized
					if ( _style == 'simple' ) {
						this.mat.append(this.temp.selectable);
					} else {
						this.mat.append(this.temp.simpleBox);
					}
					// Customized end
					this.mat.attr({
						id: this.attrs.id+'_mat'
					}).addClass(_style).addClass(this.attrs.cl);
				}

				// For rebuilding
				if ( this.generatedFlg) {
					this.mat.empty();

					if ( _style == 'simple' ) {
						this.mat.append(this.temp.selectable);
					} else {
						this.mat.append(this.temp.simpleBox);
					}
				}

				this._div = $('<div class="body"/>');
				if ( has_optgroup ) {
					this.mat.addClass('otpgroup');
					var _optgroup = $('optgroup', this.target);
					var _option = [];

					for ( var i=0; i<_optgroup.length; i++ ) {
						_option[i] = $('option', _optgroup[i]);
					}

					var _dl = $('<dl/>');

					for ( var i=0; i<_optgroup.length; i++ ) {
						var _dt = $('<dt/>');
						_dt.text($(_optgroup[i]).attr('label'));
						var _dd = $('<dd/>');
						for ( var j=0; j<_option[i].length; j++ ) {
							generate_anchors($(_option[i][j]), _dd);
						}
						_dl.append(_dt).append(_dd);
					}
					this._div.append(_dl).addClass('optg');
					$('div', this.mat).append(this._div);

				} else {
					this.mat.addClass('nooptgroup');
					var _option = $('option', this.target);
					for ( var i=0; i<_option.length; i++ ) {
						generate_anchors($(_option[i]), this._div);
					}
					$('div', this.mat).append(this._div.addClass('nooptg'));
				}

				// For rebuilding
				if ( !this.generatedFlg ) {
					$('body').append(this.mat);
					this.mat.addClass('sctble_mat').css({
						position: 'absolute',
						zIndex: 1000,
						display: 'none'
					});
					$('*:first-child',this.mat).addClass('first-child');
					$('*:last-child',this.mat).addClass('last-child');
				}

				// This is for IE6 that doesn't have "max-height" properties
				if ( document.all && typeof document.body.style.maxHeight == 'undefined' ) {
					if ( this.conf.height < this.mat.height() ) {
						$(this._div).css('height', this.conf.height);
					}
					// Other browsers
				} else {
					$(this._div).css('maxHeight', this.conf.height);
				}

				// get height of the mat
				this.mat.show();
				//this.matHeight = this.mat.attr('offsetHeight');
				this.matHeight = this.mat.height();
				this.mat.hide();
			},

			// Bind events
			// @ 09-09-17 22:59
			bind_events: function() {
				var _this = this;
				// Flag checking where the events was called
				var is_called = true;

				var set_pos = function() {
					var topPos,
						scrollTop = document.documentElement.scrollTop || document.body.scrollTop,
						clientHeight = document.documentElement.clientHeight || document.body.clientHeight,
						_pos = _this.m_input.offset();

					if ( clientHeight/2 < (_pos.top - scrollTop) ) {
						topPos = _pos.top - _this.matHeight + _this.conf.top - 5;
						console.log(_this);
					} else {
						topPos = _pos.top + _this.m_input.height()*1.3 + _this.conf.top;
					}
					_this.mat.css({
						top: topPos,
						left: _pos.left + _this.conf.left
					});
				}
				$(window).resize(function() {
					set_pos();
				});

				// Hide all mats are displayed
				var mat_hide = function() {
					var _mat = $('.sctble_mat');

					switch( _this.conf.out ) {
						case 'slideUp':
							_mat.slideUp(_this.conf.outDuration);
							break;
						case 'fadeOut':
							_mat.fadeOut(_this.conf.outDuration);
							break;
						default:
							_mat.hide();
							break;
					}
				}

				// Show the mat
				var mat_show = function() {
					mat_hide();

					if ( _this.conf.set == 'slideDown' ) {
						var scrollTop = document.documentElement.scrollTop || document.body.scrollTop,
							clientHeight = document.documentElement.clientHeight || document.body.clientHeight,
							_pos = _this.m_input.offset(),
							balance = clientHeight/2 < (_pos.top - scrollTop);

						if ( balance ) {
							_this.mat.css('top', _pos.top + _this.conf.top - 5);
						}
					}

					if ( _this.conf.set == 'slideDown' ) {
						if ( balance ) {
							_this.mat
								.animate({
									height: 'toggle',
									top: parseInt(_this.mat.css('top')) -  _this.matHeight
								}, {
									easing: 'swing',
									duration: _this.conf.setDuration
								})
								.css('opacity', _this.conf.opacity);
						} else {
							_this.mat.slideDown(_this.conf.setDuration).css('opacity', _this.conf.opacity);
						}
					} else
					if ( _this.conf.set == 'fadeIn' ) {
						_this.mat.css({
							display: 'block',
							opacity: 0
						}).fadeTo(_this.conf.setDuration, _this.conf.opacity);
					} else {
						_this.mat.show().css('opacity', _this.conf.opacity);
					}

					var _interval = isNaN(_this.conf.setDuration) ? null : _this.conf.setDuration+10;
					if( _interval == null ) {
						if ( _this.conf.setDuration.match(/slow/) ) {
							interval = 610;
						} else if ( _this.conf.setDuration.match(/normal/) ) {
							interval = 410;
						} else {
							interval = 210;
						}
					}

					var _chk = setInterval(function() {
						$('a.selected', _this.mat).focus();
						clearInterval(_chk);
					}, _interval);
				}

				// Call selectable
				this.m_input.click(function(event) {
					if ( _this.mat.is(':visible') ) return false;
					set_pos();
					$(this).addClass('sctble_focus');
					$('a.sctble_display').not(this).removeClass('sctble_focus');

					mat_show();
					event.stopPropagation();
					return false;
				}).keyup(function(event) {
						if( is_called ){
							set_pos();
							mat_show();
							event.stopPropagation();
						} else {
							is_called = true;
						}
					});

				// Stop event propagation
				this.mat.click(function(event) {
					event.stopPropagation();
				});

				// Hide the mat
				$('body, a').not('a.sctble_display').click(function(event) {
					$('a.sctble_display').removeClass('sctble_focus');
					mat_hide();
				}).not('a').keyup(function(event) {
						if ( event.keyCode=='27' ) {
							$('a.sctble_focus').removeClass('sctble_focus');
							is_called = false;
							_this.m_input.blur();
							mat_hide();
						}
					});

				// Click value append to both dummy and change original select value
				$('a', this.mat).click(function() {
					var self = $(this);
					_this.m_text.text(decodeURI(self.attr('href').split('#')[1]));
					$('option[value="'+self.attr('name')+'"]', _this.target).attr('selected', 'selected');
					$('.selected', _this.mat).removeClass('selected');
					self.addClass('selected');
					_this.m_input.removeClass('sctble_focus');
					is_called = false;
					mat_hide();

					if ( _this.conf.callback && typeof _this.conf.callback=='function' ) {
						_this.conf.callback.call(_this.target);
					}

					_this.m_input.focus();
					return false;
				});

				// Be able to click original select label
				$('label[for="'+this.attrs.id+'"]').click(function(event) {
					set_pos();
					_this.m_input.addClass('sctble_focus');
					$('a.sctble_focus').not(_this.m_input).removeClass('sctble_focus');
					mat_show();
					event.stopPropagation();
					return false;
				});
			}
		}

		// Extense the namespace of jQuery as method
		// This function returns (the) instance(s)
		$.fn.jQselectable = function(options, temp) {
			if ( $(this).length>1 ) {
				var _instances = [];
				$(this).each(function(i) {
					_instances[i] = new jQselectable(this, options, temp);
				});
				return _instances;
			} else {
				return new jQselectable(this, options, temp);
			}
		}

		// If namespace of jQuery.fn has 'selectable', this is 'jQselectable'
		// To prevent the interference of namespace
		// You can call 'selectable' method by both 'jQuery.fn.selectable' and 'jQuery.fn.jQselectable' you like
		if ( !jQuery.fn.selectable ) {
			$.fn.selectable = $.fn.jQselectable;
		}

	})(jQuery);
	return jQuery;
});